# load required libraries

# to use harry potter dataset
# devtools::install_github("bradleyboehmke/harrypotter")
# devtools::install_github("quanteda/quanteda.sentiment")
# devtools::install_github("quanteda/quanteda.corpora")

library(quanteda)
Package version: 3.1.0
Unicode version: 13.0
ICU version: 69.1
Parallel computing: 8 of 8 threads used.
See https://quanteda.io for tutorials and examples.

Attache Paket: ‘quanteda’

Das folgende Objekt ist maskiert durch ‘.GlobalEnv’:

    data_dictionary_LSD2015
library(readtext)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
library(corpus)
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ─────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.1 ──
✓ ggplot2 3.3.5     ✓ purrr   0.3.4
✓ tibble  3.1.4     ✓ dplyr   1.0.7
✓ tidyr   1.1.3     ✓ stringr 1.4.0
✓ readr   2.0.1     ✓ forcats 0.5.1
── Conflicts ────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
library(stringr)
library(tidytext)
library(harrypotter)
library(dplyr)
library(quanteda.sentiment)

Attache Paket: ‘quanteda.sentiment’

Das folgende Objekt ist maskiert ‘package:quanteda’:

    data_dictionary_LSD2015
library(vader)


require(quanteda)
require(quanteda.corpora)
Lade nötiges Paket: quanteda.corpora
require(quanteda.sentiment)
afinn2 <- data_dictionary_AFINN

Harry Potter - Dataset

# load harry potter dataset 
titles <- c("Philosopher's Stone", "Chamber of Secrets", "Prisoner of Azkaban",
            "Goblet of Fire", "Order of the Phoenix", "Half-Blood Prince",
            "Deathly Hallows")

books <- list(philosophers_stone, chamber_of_secrets, prisoner_of_azkaban,
           goblet_of_fire, order_of_the_phoenix, half_blood_prince,
           deathly_hallows)
  
series <- tibble()

for(i in seq_along(titles)) {
        
        clean <- tibble(chapter = seq_along(books[[i]]),
                        text = books[[i]]) %>%
             #unnest_tokens(word, text) %>%
             mutate(book = titles[i]) %>%
             select(book, everything())

        series <- rbind(series, clean)
}

series$book <- factor(series$book, levels = rev(titles))

series
#book_groups <- series %>% group_by(book, chapter)

Harry Potter - AFINN Lexicon

afinn_hp2 <- series %>%
        group_by(book, chapter) %>% # add word for single word scores 
        inner_join(get_sentiments("afinn")) %>%
        group_by(book, chapter) %>% # add word for single word scores
        #summarise(sentiment = sum(value)) %>%
        summarise(sentiment = mean(value, na.rm = TRUE)) %>%
        mutate(method = "AFINN")  %>%
        ggplot(aes(chapter, sentiment, fill = book)) +
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          facet_wrap(~ book, ncol = 2, scales = "free_x")

afinn_hp2

#ggsave(plot = afinn, width = 15, height = 15, dpi = 300, filename = "afinn_hp_mean.png")

Lexicoder: HP


hp1_lsd.df <- as.data.frame.matrix(hp1_lsd)

hp1_lsd.df$chapter <- 1:nrow(hp1_lsd.df)

plot <- ggplot(hp1_lsd, aes(x =hp1_lsd.df$chapter, y=sentiment)) +
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE)
plot + ylim(-1.0, 1.0) + labs(y="sentiment", x = "chapter") + ggtitle("HP1 - Lexicoder")


#hp1_lsd.df

AFINN: HP

hp1_afinn2 <- textstat_valence(hp1_tokenized, afinn2, normalize="dictionary")

hp1_afinn2.df <- as.data.frame.matrix(hp1_afinn2)

hp1_afinn2.df$chapter <- 1:nrow(hp1_afinn2.df)

plot <- ggplot(hp1_afinn2.df, aes(x =hp1_afinn2.df$chapter, y=sentiment)) +
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE)
plot + ylim(-1.0, 1.0) + labs(y="sentiment", x = "chapter") + ggtitle("HP1 - AFINN")
Warnung: Use of `hp1_afinn2.df$chapter` is discouraged. Use `chapter` instead.

#hp1_afinn2

VADER: HP

QUANTEDA.SENTIMENT

AFINN: HP

# Work with quanteda.sentiment on HP corpus:

# convert tibble to dataframe
series.df <- as.data.frame(series)

# tokenize books
series_tokenized <- series.df %>%
  unnest_tokens(tokens, text)

# apply afinn lexicon
series_tokenized$afinn2 <- textstat_valence(series_tokenized$tokens, afinn2)$sentiment

# replace all 0 values with na
series_tokenized[series_tokenized == 0] <- NA

series_tokenized %>%
  group_by(book, chapter) %>% # group df by book and chapter to get sentiment per chapter
  summarise(sentiment = mean(afinn2, na.rm = TRUE)) %>% # calculate mean w/o regarding na values
  mutate(method = "AFINN") %>% # add column with method 
        ggplot(aes(chapter, sentiment, fill = book)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          facet_wrap(~ book, ncol = 2, scales = "free_x") +
          ggtitle("AFINN HP")
`summarise()` has grouped output by 'book'. You can override using the `.groups` argument.

Lexicoder: HP

# Work with quanteda.sentiment on HP corpus:

# apply lexicoder lexicon
series$lsd <- textstat_polarity(tokens(series$text), data_dictionary_LSD2015)$sentiment 

#series.df <- as.data.frame(series)

plot <- ggplot(series, aes(chapter, lsd, fill = book)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          facet_wrap(~ book, ncol = 2, scales = "free_x") +
          ggtitle("Lexicoder HP")
plot 

Vader: HP

REVIEWS DATASET

# load dataset
reviews <- readtext("datasets/goodreads_reviews_children_2.json", text_field = "review_text")

# convert to dataframe
reviews.df <- as.data.frame(reviews)

# add doc_id (i.e. according to index)
reviews.df$doc_id <- 1:nrow(reviews.df)

Sample Dataset

# get random sample of 50 reviews 
reviews_sample <- reviews.df[sample(1:nrow(reviews.df), 50,
   replace=FALSE),]

# get first 50 rows of data 
reviews_50 <- head(reviews.df,50)
reviews_50 = subset(reviews_50, select = c(doc_id,text,rating))
reviews_50

Get Translations of Dataset

# either via corpus 
reviews.corpus <- corpus(reviews)
docvars(reviews.corpus, "language") <- textcat(reviews.corpus)
reviews_en <- corpus_subset(reviews.corpus, language == "english", drop_docid = TRUE)

# or via dataframe logic
reviews.df$language <- textcat(reviews.df$text)

Sentiment Analysis on Reviews Dataset

AFINN

# Afinn
# tokenize 
reviews_tokenized <- reviews_50 %>%
  unnest_tokens(tokens, text)

# apply afinn lexicon
reviews_tokenized$afinn2 <- textstat_valence(reviews_tokenized$tokens, afinn2)$sentiment

# replace all 0 values with na
reviews_tokenized[reviews_tokenized == 0] <- NA

# calculate mean scores for tokens per doc
afinn_scores <- reviews_tokenized %>%
  group_by(doc_id) %>% # group df by doc_id to get mean sentiment score
  summarise(total = mean(afinn2, na.rm = TRUE)) #%>% # calculate mean w/o regarding na values

# add afinn scores to df 
reviews_50$afinn <- afinn_scores$total

# different version to get plot 
reviews_tokenized %>%
  group_by(doc_id) %>% # group df by book and chapter to get sentiment per chapter
  #reviews_tokenized$sentiment = mean(afinn2, na.rm = TRUE) %>%
  summarise(sentiment = mean(afinn2, na.rm = TRUE)) %>% # calculate mean w/o regarding na values
  mutate(method = "AFINN") %>% # add column with method 
        ggplot(aes(doc_id, sentiment, fill = doc_id)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          #facet_wrap(~ doc_id, ncol = 2, scales = "free_x") +
          ggtitle("AFINN Reviews")
Warnung: Removed 1 rows containing missing values (position_stack).

LEXICODER

# apply lexicoder lexicon
reviews_50$lsd <- textstat_polarity(tokens(reviews_50$text), data_dictionary_LSD2015)$sentiment 
#series.df <- as.data.frame(series)

plot <- ggplot(reviews_50, aes(doc_id, lsd, fill = doc_id)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          #facet_wrap(~ doc_id, ncol = 2, scales = "free_x") +
          ggtitle("Lexicoder Reviews")
plot 

Vader

Statistics

# convert to binary results
reviews_50$afinn_binary[reviews_50$afinn > 0] <- "pos"
reviews_50$afinn_binary[reviews_50$afinn <= 0] <- "neg"

reviews_50$lsd_binary[reviews_50$lsd > 0] <- "pos"
reviews_50$lsd_binary[reviews_50$lsd <= 0] <- "neg"

reviews_50$vader_binary[reviews_50$vader > 0] <- "pos"
reviews_50$vader_binary[reviews_50$vader <= 0] <- "neg"

reviews_50$rating_binary[reviews_50$rating >= 3] <- "pos"
reviews_50$rating_binary[reviews_50$rating < 3] <- "neg"

#reviews_50[reviews_50 == NA] <- "neu"
actual_values <- test$rating_binary
predict_values <- test$afinn_binary

# create confusion matrix 
confusion_matrix <- table(ACTUAL=actual_values, PREDICTED=predict_values)

# assign values from matrix to true/false positives/negatives
TN <- confusion_matrix[1]
FN <- confusion_matrix[2]
FP <- confusion_matrix[3]
TP <- confusion_matrix[4]

# calculate statistics
precision <- TP/(TP+FP)
accuracy <- (TP+TN)/(TP+TN+FP+FN)
recall <- TP/(TP+FN)
F1 <- (2*precision*recall)/(precision+recall)
get_statistics <- function(df) {
  statistics <- data.frame(matrix(ncol=4, nrow=0))
  x <- c("accuracy", "precision", "recall", "F1")
  colnames(statistics) <- x
  lex1 <- "afinn_binary"
  lex2 <- "lsd_binary"
  lex3 <- "vader_binary"
  gold <- "rating_binary"
  
  lexicons <- c(lex1,lex2,lex3)
  
  for(lex in lexicons){
    confusion_matrix <- table(ACTUAL=df[[gold]], PREDICTED=df[[lex]])
    TN <- confusion_matrix[1]
    FN <- confusion_matrix[2]
    FP <- confusion_matrix[3]
    TP <- confusion_matrix[4]
  
    # calculate statistics
    precision <- TP/(TP+FP)
    accuracy <- (TP+TN)/(TP+TN+FP+FN)
    recall <- TP/(TP+FN)
    F1 <- (2*precision*recall)/(precision+recall)
  
    # add to table
    output <- c(accuracy,precision, recall, F1)
    statistics[lex,] = rbind(statistics[[lex]], output)
    }
    
  return(statistics)
  
}

get_statistics(test)
test[["vader_binary"]]
lexicons <- list(test$afinn_binary, test$lsd_binary)

for(lex in lexicons){
  print(lex)
}
LS0tCnRpdGxlOiAiQ29tcGFyaXNvbiBvZiBTZW50aW1lbnQgVG9vbHMgYWNyb3NzIERvbWFpbnMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KYGBge3J9CiMgbG9hZCByZXF1aXJlZCBsaWJyYXJpZXMKCiMgdG8gdXNlIGhhcnJ5IHBvdHRlciBkYXRhc2V0CiMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJicmFkbGV5Ym9laG1rZS9oYXJyeXBvdHRlciIpCiMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJxdWFudGVkYS9xdWFudGVkYS5zZW50aW1lbnQiKQojIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigicXVhbnRlZGEvcXVhbnRlZGEuY29ycG9yYSIpCgpsaWJyYXJ5KHF1YW50ZWRhKQpsaWJyYXJ5KHJlYWR0ZXh0KQpsaWJyYXJ5KGNvcnB1cykKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeSh0aWR5dGV4dCkKbGlicmFyeShoYXJyeXBvdHRlcikKbGlicmFyeShkcGx5cikKbGlicmFyeShxdWFudGVkYS5zZW50aW1lbnQpCmxpYnJhcnkodmFkZXIpCgoKcmVxdWlyZShxdWFudGVkYSkKcmVxdWlyZShxdWFudGVkYS5jb3Jwb3JhKQpyZXF1aXJlKHF1YW50ZWRhLnNlbnRpbWVudCkKI2xpYnJhcnkoInF1YW50ZWRhIiwgd2Fybi5jb25mbGljdHMgPSBGQUxTRSwgcXVpZXRseSA9IFRSVUUpCmBgYApgYGB7cn0KYWZpbm4yIDwtIGRhdGFfZGljdGlvbmFyeV9BRklOTgoKYGBgCgojIEhhcnJ5IFBvdHRlciAtIERhdGFzZXQKYGBge3J9CiMgbG9hZCBoYXJyeSBwb3R0ZXIgZGF0YXNldCAKdGl0bGVzIDwtIGMoIlBoaWxvc29waGVyJ3MgU3RvbmUiLCAiQ2hhbWJlciBvZiBTZWNyZXRzIiwgIlByaXNvbmVyIG9mIEF6a2FiYW4iLAogICAgICAgICAgICAiR29ibGV0IG9mIEZpcmUiLCAiT3JkZXIgb2YgdGhlIFBob2VuaXgiLCAiSGFsZi1CbG9vZCBQcmluY2UiLAogICAgICAgICAgICAiRGVhdGhseSBIYWxsb3dzIikKCmJvb2tzIDwtIGxpc3QocGhpbG9zb3BoZXJzX3N0b25lLCBjaGFtYmVyX29mX3NlY3JldHMsIHByaXNvbmVyX29mX2F6a2FiYW4sCiAgICAgICAgICAgZ29ibGV0X29mX2ZpcmUsIG9yZGVyX29mX3RoZV9waG9lbml4LCBoYWxmX2Jsb29kX3ByaW5jZSwKICAgICAgICAgICBkZWF0aGx5X2hhbGxvd3MpCiAgCnNlcmllcyA8LSB0aWJibGUoKQoKZm9yKGkgaW4gc2VxX2Fsb25nKHRpdGxlcykpIHsKICAgICAgICAKICAgICAgICBjbGVhbiA8LSB0aWJibGUoY2hhcHRlciA9IHNlcV9hbG9uZyhib29rc1tbaV1dKSwKICAgICAgICAgICAgICAgICAgICAgICAgdGV4dCA9IGJvb2tzW1tpXV0pICU+JQogICAgICAgICAgICAgI3VubmVzdF90b2tlbnMod29yZCwgdGV4dCkgJT4lCiAgICAgICAgICAgICBtdXRhdGUoYm9vayA9IHRpdGxlc1tpXSkgJT4lCiAgICAgICAgICAgICBzZWxlY3QoYm9vaywgZXZlcnl0aGluZygpKQoKICAgICAgICBzZXJpZXMgPC0gcmJpbmQoc2VyaWVzLCBjbGVhbikKfQoKc2VyaWVzJGJvb2sgPC0gZmFjdG9yKHNlcmllcyRib29rLCBsZXZlbHMgPSByZXYodGl0bGVzKSkKCnNlcmllcwojYm9va19ncm91cHMgPC0gc2VyaWVzICU+JSBncm91cF9ieShib29rLCBjaGFwdGVyKQojIHRva2VuaXplIGhwMQojaHAxX3Rva2VuaXplZCA8LSB0b2tlbnNfdG9sb3dlcih0b2tlbnMocGhpbG9zb3BoZXJzX3N0b25lLCByZW1vdmVfcHVuY3QgPSBUUlVFKSkgCmBgYAojIyMgSGFycnkgUG90dGVyIC0gQUZJTk4gTGV4aWNvbgpgYGB7cn0KYWZpbm5faHAyIDwtIHNlcmllcyAlPiUKICAgICAgICBncm91cF9ieShib29rLCBjaGFwdGVyKSAlPiUgIyBhZGQgd29yZCBmb3Igc2luZ2xlIHdvcmQgc2NvcmVzIAogICAgICAgIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImFmaW5uIikpICU+JQogICAgICAgIGdyb3VwX2J5KGJvb2ssIGNoYXB0ZXIpICU+JSAjIGFkZCB3b3JkIGZvciBzaW5nbGUgd29yZCBzY29yZXMKICAgICAgICAjc3VtbWFyaXNlKHNlbnRpbWVudCA9IHN1bSh2YWx1ZSkpICU+JQogICAgICAgIHN1bW1hcmlzZShzZW50aW1lbnQgPSBtZWFuKHZhbHVlLCBuYS5ybSA9IFRSVUUpKSAlPiUKICAgICAgICBtdXRhdGUobWV0aG9kID0gIkFGSU5OIikgICU+JQogICAgICAgIGdncGxvdChhZXMoY2hhcHRlciwgc2VudGltZW50LCBmaWxsID0gYm9vaykpICsKICAgICAgICAgIGdlb21fYmFyKGFscGhhID0gMC44LCBzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogICAgICAgICAgZmFjZXRfd3JhcCh+IGJvb2ssIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZV94IikKCmFmaW5uX2hwMgoKI2dnc2F2ZShwbG90ID0gYWZpbm4sIHdpZHRoID0gMTUsIGhlaWdodCA9IDE1LCBkcGkgPSAzMDAsIGZpbGVuYW1lID0gImFmaW5uX2hwX21lYW4ucG5nIikKYGBgCgojIExleGljb2RlcjogSFAKYGBge3J9CiMgc2VsZWN0IG9ubHkgdGhlICJuZWdhdGl2ZSIgYW5kICJwb3NpdGl2ZSIgY2F0ZWdvcmllcwojZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTVfcG9zX25lZyA8LSBkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNVsxOjJdCiNocDFfbHNkIDwtIHRva2Vuc19sb29rdXAoaHAxX3Rva2VuaXplZCwgZGljdGlvbmFyeSA9IGRhdGFfZGljdGlvbmFyeV9MU0QyMDE1X3Bvc19uZWcpCgpwb2xhcml0eShkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNSkgPC0gCiAgbGlzdChwb3MgPSBjKCJwb3NpdGl2ZSIsICJuZWdfbmVnYXRpdmUiKSwgbmVnID0gYygibmVnYXRpdmUiLCAibmVnX3Bvc2l0aXZlIikpCgpocDFfbHNkIDwtIHRleHRzdGF0X3BvbGFyaXR5KGhwMV90b2tlbml6ZWQsIGRhdGFfZGljdGlvbmFyeV9MU0QyMDE1KQoKaHAxX2xzZF90b2tlbnMgPC0gdG9rZW5zX2xvb2t1cChocDFfdG9rZW5pemVkLCBkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNSwgbmVzdGVkX3Njb3BlID0gImRpY3Rpb25hcnkiLCBleGNsdXNpdmUgPSBGQUxTRSkKaHAxX2xzZC5kZiA8LSBhcy5kYXRhLmZyYW1lLm1hdHJpeChocDFfbHNkKQpocDFfbHNkLmRmJGNoYXB0ZXIgPC0gMTpucm93KGhwMV9sc2QuZGYpCgpwbG90IDwtIGdncGxvdChocDFfbHNkLCBhZXMoeCA9aHAxX2xzZC5kZiRjaGFwdGVyLCB5PXNlbnRpbWVudCkpICsKICAgICAgICAgIGdlb21fYmFyKGFscGhhID0gMC44LCBzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkKcGxvdCArIHlsaW0oLTEuMCwgMS4wKSArIGxhYnMoeT0ic2VudGltZW50IiwgeCA9ICJjaGFwdGVyIikgKyBnZ3RpdGxlKCJIUDEgLSBMZXhpY29kZXIiKQpgYGAKIyBBRklOTjogSFAKYGBge3J9CmhwMV9hZmlubjIgPC0gdGV4dHN0YXRfdmFsZW5jZShocDFfdG9rZW5pemVkLCBhZmlubjIsIG5vcm1hbGl6ZT0iZGljdGlvbmFyeSIpCgpocDFfYWZpbm4yLmRmIDwtIGFzLmRhdGEuZnJhbWUubWF0cml4KGhwMV9hZmlubjIpCmhwMV9hZmlubjIuZGYkY2hhcHRlciA8LSAxOm5yb3coaHAxX2FmaW5uMi5kZikKCnBsb3QgPC0gZ2dwbG90KGhwMV9hZmlubjIuZGYsIGFlcyh4ID1ocDFfYWZpbm4yLmRmJGNoYXB0ZXIsIHk9c2VudGltZW50KSkgKwogICAgICAgICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKQpwbG90ICsgeWxpbSgtMS4wLCAxLjApICsgbGFicyh5PSJzZW50aW1lbnQiLCB4ID0gImNoYXB0ZXIiKSArIGdndGl0bGUoIkhQMSAtIEFGSU5OIikKYGBgCiMgVkFERVI6IEhQCmBgYHtyfQpnZXRfdmFkZXIocGhpbG9zb3BoZXJzX3N0b25lWzFdKQoKaHAxX3ZhZGVyIDwtIHZhZGVyX2RmKHBoaWxvc29waGVyc19zdG9uZSkKaHAxX3ZhZGVyJGNoYXB0ZXIgPC0gMTpucm93KGhwMV92YWRlcikKCnBsb3QgPC0gZ2dwbG90KGhwMV92YWRlciwgYWVzKHggPWNoYXB0ZXIsIHk9Y29tcG91bmQpKSArCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpCnBsb3QgKyB5bGltKC01LjAsIDUuMCkgKyBsYWJzKHk9InNlbnRpbWVudCIsIHggPSAiY2hhcHRlciIpICsgZ2d0aXRsZSgiSFAxIC0gVkFERVIiKQpgYGAKIyBRVUFOVEVEQS5TRU5USU1FTlQKIyBBRklOTjogSFAKYGBge3J9CiMgV29yayB3aXRoIHF1YW50ZWRhLnNlbnRpbWVudCBvbiBIUCBjb3JwdXM6CiMgY29udmVydCB0aWJibGUgdG8gZGF0YWZyYW1lCnNlcmllcy5kZiA8LSBhcy5kYXRhLmZyYW1lKHNlcmllcykKCiMgdG9rZW5pemUgYm9va3MKc2VyaWVzX3Rva2VuaXplZCA8LSBzZXJpZXMuZGYgJT4lCiAgdW5uZXN0X3Rva2Vucyh0b2tlbnMsIHRleHQpCgojIGFwcGx5IGFmaW5uIGxleGljb24Kc2VyaWVzX3Rva2VuaXplZCRhZmlubjIgPC0gdGV4dHN0YXRfdmFsZW5jZShzZXJpZXNfdG9rZW5pemVkJHRva2VucywgYWZpbm4yKSRzZW50aW1lbnQKCiMgcmVwbGFjZSBhbGwgMCB2YWx1ZXMgd2l0aCBuYQpzZXJpZXNfdG9rZW5pemVkW3Nlcmllc190b2tlbml6ZWQgPT0gMF0gPC0gTkEKCnNlcmllc190b2tlbml6ZWQgJT4lCiAgZ3JvdXBfYnkoYm9vaywgY2hhcHRlcikgJT4lICMgZ3JvdXAgZGYgYnkgYm9vayBhbmQgY2hhcHRlciB0byBnZXQgc2VudGltZW50IHBlciBjaGFwdGVyCiAgc3VtbWFyaXNlKHNlbnRpbWVudCA9IG1lYW4oYWZpbm4yLCBuYS5ybSA9IFRSVUUpKSAlPiUgIyBjYWxjdWxhdGUgbWVhbiB3L28gcmVnYXJkaW5nIG5hIHZhbHVlcwogIG11dGF0ZShtZXRob2QgPSAiQUZJTk4iKSAlPiUgIyBhZGQgY29sdW1uIHdpdGggbWV0aG9kIAogICAgICAgIGdncGxvdChhZXMoY2hhcHRlciwgc2VudGltZW50LCBmaWxsID0gYm9vaykpICsgIyBwbG90IHNlbnRpbWVudCBvZiBib29rcwogICAgICAgICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICAgICAgICBmYWNldF93cmFwKH4gYm9vaywgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgICAgICAgICBnZ3RpdGxlKCJBRklOTiBIUCIpCmBgYAojIExleGljb2RlcjogSFAgIApgYGB7cn0KIyBXb3JrIHdpdGggcXVhbnRlZGEuc2VudGltZW50IG9uIEhQIGNvcnB1czoKIyBhcHBseSBsZXhpY29kZXIgbGV4aWNvbgpzZXJpZXMkbHNkIDwtIHRleHRzdGF0X3BvbGFyaXR5KHRva2VucyhzZXJpZXMkdGV4dCksIGRhdGFfZGljdGlvbmFyeV9MU0QyMDE1KSRzZW50aW1lbnQgCgojc2VyaWVzLmRmIDwtIGFzLmRhdGEuZnJhbWUoc2VyaWVzKQoKcGxvdCA8LSBnZ3Bsb3Qoc2VyaWVzLCBhZXMoY2hhcHRlciwgbHNkLCBmaWxsID0gYm9vaykpICsgIyBwbG90IHNlbnRpbWVudCBvZiBib29rcwogICAgICAgICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICAgICAgICBmYWNldF93cmFwKH4gYm9vaywgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgICAgICAgICBnZ3RpdGxlKCJMZXhpY29kZXIgSFAiKQpwbG90IApgYGAKCiMgVmFkZXI6IEhQCmBgYHtyfQojIGFwcGx5IHZhZGVyIGxleGljb24gdG8gYWxsIEhQIGJvb2tzCnNlcmllcyR2YWRlciA8LSB2YWRlcl9kZihzZXJpZXMkdGV4dCkkY29tcG91bmQKCiNzZXJpZXMuZGYgPC0gYXMuZGF0YS5mcmFtZShzZXJpZXMpCgpwbG90IDwtIGdncGxvdChzZXJpZXMsIGFlcyhjaGFwdGVyLCBsc2QsIGZpbGwgPSBib29rKSkgKyAjIHBsb3Qgc2VudGltZW50IG9mIGJvb2tzCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgIGZhY2V0X3dyYXAofiBib29rLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpICsKICAgICAgICAgIGdndGl0bGUoIlZBREVSIEhQIikKcGxvdCAKYGBgCgojIFJFVklFV1MgREFUQVNFVApgYGB7cn0KIyBsb2FkIGRhdGFzZXQKcmV2aWV3cyA8LSByZWFkdGV4dCgiZGF0YXNldHMvZ29vZHJlYWRzX3Jldmlld3NfY2hpbGRyZW5fMi5qc29uIiwgdGV4dF9maWVsZCA9ICJyZXZpZXdfdGV4dCIpCgojIGNvbnZlcnQgdG8gZGF0YWZyYW1lCnJldmlld3MuZGYgPC0gYXMuZGF0YS5mcmFtZShyZXZpZXdzKQoKIyBhZGQgZG9jX2lkIChpLmUuIGFjY29yZGluZyB0byBpbmRleCkKcmV2aWV3cy5kZiRkb2NfaWQgPC0gMTpucm93KHJldmlld3MuZGYpCmBgYAoKIyMjIFNhbXBsZSBEYXRhc2V0CmBgYHtyfQojIGdldCByYW5kb20gc2FtcGxlIG9mIDUwIHJldmlld3MgCnJldmlld3Nfc2FtcGxlIDwtIHJldmlld3MuZGZbc2FtcGxlKDE6bnJvdyhyZXZpZXdzLmRmKSwgNTAsCiAgIHJlcGxhY2U9RkFMU0UpLF0KCiMgZ2V0IGZpcnN0IDUwIHJvd3Mgb2YgZGF0YSAKcmV2aWV3c181MCA8LSBoZWFkKHJldmlld3MuZGYsNTApCnJldmlld3NfNTAgPSBzdWJzZXQocmV2aWV3c181MCwgc2VsZWN0ID0gYyhkb2NfaWQsdGV4dCxyYXRpbmcpKQpgYGAKIyMjIEdldCBUcmFuc2xhdGlvbnMgb2YgRGF0YXNldCAKYGBge3J9CiMgZWl0aGVyIHZpYSBjb3JwdXMgCnJldmlld3MuY29ycHVzIDwtIGNvcnB1cyhyZXZpZXdzKQpkb2N2YXJzKHJldmlld3MuY29ycHVzLCAibGFuZ3VhZ2UiKSA8LSB0ZXh0Y2F0KHJldmlld3MuY29ycHVzKQpyZXZpZXdzX2VuIDwtIGNvcnB1c19zdWJzZXQocmV2aWV3cy5jb3JwdXMsIGxhbmd1YWdlID09ICJlbmdsaXNoIiwgZHJvcF9kb2NpZCA9IFRSVUUpCgojIG9yIHZpYSBkYXRhZnJhbWUgbG9naWMKcmV2aWV3cy5kZiRsYW5ndWFnZSA8LSB0ZXh0Y2F0KHJldmlld3MuZGYkdGV4dCkKYGBgCgojIyMgU2VudGltZW50IEFuYWx5c2lzIG9uIFJldmlld3MgRGF0YXNldAoKIyMjIyBBRklOTgpgYGB7cn0KIyBBZmlubgojIHRva2VuaXplIApyZXZpZXdzX3Rva2VuaXplZCA8LSByZXZpZXdzXzUwICU+JQogIHVubmVzdF90b2tlbnModG9rZW5zLCB0ZXh0KQoKIyBhcHBseSBhZmlubiBsZXhpY29uCnJldmlld3NfdG9rZW5pemVkJGFmaW5uMiA8LSB0ZXh0c3RhdF92YWxlbmNlKHJldmlld3NfdG9rZW5pemVkJHRva2VucywgYWZpbm4yKSRzZW50aW1lbnQKCiMgcmVwbGFjZSBhbGwgMCB2YWx1ZXMgd2l0aCBuYQpyZXZpZXdzX3Rva2VuaXplZFtyZXZpZXdzX3Rva2VuaXplZCA9PSAwXSA8LSBOQQoKIyBjYWxjdWxhdGUgbWVhbiBzY29yZXMgZm9yIHRva2VucyBwZXIgZG9jCmFmaW5uX3Njb3JlcyA8LSByZXZpZXdzX3Rva2VuaXplZCAlPiUKICBncm91cF9ieShkb2NfaWQpICU+JSAjIGdyb3VwIGRmIGJ5IGRvY19pZCB0byBnZXQgbWVhbiBzZW50aW1lbnQgc2NvcmUKICBzdW1tYXJpc2UodG90YWwgPSBtZWFuKGFmaW5uMiwgbmEucm0gPSBUUlVFKSkgIyU+JSAjIGNhbGN1bGF0ZSBtZWFuIHcvbyByZWdhcmRpbmcgbmEgdmFsdWVzCgojIGFkZCBhZmlubiBzY29yZXMgdG8gZGYgCnJldmlld3NfNTAkYWZpbm4gPC0gYWZpbm5fc2NvcmVzJHRvdGFsCgojIGRpZmZlcmVudCB2ZXJzaW9uIHRvIGdldCBwbG90IApyZXZpZXdzX3Rva2VuaXplZCAlPiUKICBncm91cF9ieShkb2NfaWQpICU+JSAjIGdyb3VwIGRmIGJ5IGJvb2sgYW5kIGNoYXB0ZXIgdG8gZ2V0IHNlbnRpbWVudCBwZXIgY2hhcHRlcgogICNyZXZpZXdzX3Rva2VuaXplZCRzZW50aW1lbnQgPSBtZWFuKGFmaW5uMiwgbmEucm0gPSBUUlVFKSAlPiUKICBzdW1tYXJpc2Uoc2VudGltZW50ID0gbWVhbihhZmlubjIsIG5hLnJtID0gVFJVRSkpICU+JSAjIGNhbGN1bGF0ZSBtZWFuIHcvbyByZWdhcmRpbmcgbmEgdmFsdWVzCiAgbXV0YXRlKG1ldGhvZCA9ICJBRklOTiIpICU+JSAjIGFkZCBjb2x1bW4gd2l0aCBtZXRob2QgCiAgICAgICAgZ2dwbG90KGFlcyhkb2NfaWQsIHNlbnRpbWVudCwgZmlsbCA9IGRvY19pZCkpICsgIyBwbG90IHNlbnRpbWVudCBvZiBib29rcwogICAgICAgICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICAgICAgICAjZmFjZXRfd3JhcCh+IGRvY19pZCwgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgICAgICAgICBnZ3RpdGxlKCJBRklOTiBSZXZpZXdzIikKYGBgCiMjIyMgTEVYSUNPREVSCmBgYHtyfQojIGFwcGx5IGxleGljb2RlciBsZXhpY29uCnJldmlld3NfNTAkbHNkIDwtIHRleHRzdGF0X3BvbGFyaXR5KHRva2VucyhyZXZpZXdzXzUwJHRleHQpLCBkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNSkkc2VudGltZW50IAojc2VyaWVzLmRmIDwtIGFzLmRhdGEuZnJhbWUoc2VyaWVzKQoKcGxvdCA8LSBnZ3Bsb3QocmV2aWV3c181MCwgYWVzKGRvY19pZCwgbHNkLCBmaWxsID0gZG9jX2lkKSkgKyAjIHBsb3Qgc2VudGltZW50IG9mIGJvb2tzCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgICNmYWNldF93cmFwKH4gZG9jX2lkLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpICsKICAgICAgICAgIGdndGl0bGUoIkxleGljb2RlciBSZXZpZXdzIikKcGxvdCAKYGBgCiMjIyMgVmFkZXIKYGBge3J9CnJldmlld3NfNTAkdmFkZXIgPC0gdmFkZXJfZGYocmV2aWV3c181MCR0ZXh0KSRjb21wb3VuZAoKcGxvdCA8LSBnZ3Bsb3QocmV2aWV3c181MCwgYWVzKGRvY19pZCwgdmFkZXIsIGZpbGwgPSBkb2NfaWQpKSArICMgcGxvdCBzZW50aW1lbnQgb2YgYm9va3MKICAgICAgICAgIGdlb21fYmFyKGFscGhhID0gMC44LCBzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogICAgICAgICAgI2ZhY2V0X3dyYXAofiBkb2NfaWQsIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZV94IikgKwogICAgICAgICAgZ2d0aXRsZSgiVmFkZXIgUmV2aWV3cyIpCnBsb3QgCmBgYAojIFN0YXRpc3RpY3MgCmBgYHtyfQojIGNvbnZlcnQgdG8gYmluYXJ5IHJlc3VsdHMKcmV2aWV3c181MCRhZmlubl9iaW5hcnlbcmV2aWV3c181MCRhZmlubiA+IDBdIDwtICJwb3MiCnJldmlld3NfNTAkYWZpbm5fYmluYXJ5W3Jldmlld3NfNTAkYWZpbm4gPD0gMF0gPC0gIm5lZyIKCnJldmlld3NfNTAkbHNkX2JpbmFyeVtyZXZpZXdzXzUwJGxzZCA+IDBdIDwtICJwb3MiCnJldmlld3NfNTAkbHNkX2JpbmFyeVtyZXZpZXdzXzUwJGxzZCA8PSAwXSA8LSAibmVnIgoKcmV2aWV3c181MCR2YWRlcl9iaW5hcnlbcmV2aWV3c181MCR2YWRlciA+IDBdIDwtICJwb3MiCnJldmlld3NfNTAkdmFkZXJfYmluYXJ5W3Jldmlld3NfNTAkdmFkZXIgPD0gMF0gPC0gIm5lZyIKCnJldmlld3NfNTAkcmF0aW5nX2JpbmFyeVtyZXZpZXdzXzUwJHJhdGluZyA+PSAzXSA8LSAicG9zIgpyZXZpZXdzXzUwJHJhdGluZ19iaW5hcnlbcmV2aWV3c181MCRyYXRpbmcgPCAzXSA8LSAibmVnIgoKIyBvcHRpb25hbGx5OiBjb252ZXJ0IDAgPSBuZWdhdGl2ZSwgMSA9IHBvc2l0aXZlCnJldmlld3NfNTBbcmV2aWV3c181MCA9PSAicG9zIl0gPC0gMQpyZXZpZXdzXzUwW3Jldmlld3NfNTAgPT0gIm5lZyJdIDwtIDAKYGBgCgpgYGB7cn0KYWN0dWFsX3ZhbHVlcyA8LSB0ZXN0JHJhdGluZ19iaW5hcnkKcHJlZGljdF92YWx1ZXMgPC0gdGVzdCRhZmlubl9iaW5hcnkKCiMgY3JlYXRlIGNvbmZ1c2lvbiBtYXRyaXggCmNvbmZ1c2lvbl9tYXRyaXggPC0gdGFibGUoQUNUVUFMPWFjdHVhbF92YWx1ZXMsIFBSRURJQ1RFRD1wcmVkaWN0X3ZhbHVlcykKCiMgYXNzaWduIHZhbHVlcyBmcm9tIG1hdHJpeCB0byB0cnVlL2ZhbHNlIHBvc2l0aXZlcy9uZWdhdGl2ZXMKVE4gPC0gY29uZnVzaW9uX21hdHJpeFsxXQpGTiA8LSBjb25mdXNpb25fbWF0cml4WzJdCkZQIDwtIGNvbmZ1c2lvbl9tYXRyaXhbM10KVFAgPC0gY29uZnVzaW9uX21hdHJpeFs0XQoKIyBjYWxjdWxhdGUgc3RhdGlzdGljcwpwcmVjaXNpb24gPC0gVFAvKFRQK0ZQKQphY2N1cmFjeSA8LSAoVFArVE4pLyhUUCtUTitGUCtGTikKcmVjYWxsIDwtIFRQLyhUUCtGTikKRjEgPC0gKDIqcHJlY2lzaW9uKnJlY2FsbCkvKHByZWNpc2lvbityZWNhbGwpCmBgYAoKYGBge3J9CmdldF9zdGF0aXN0aWNzIDwtIGZ1bmN0aW9uKGRmKSB7CiAgc3RhdGlzdGljcyA8LSBkYXRhLmZyYW1lKG1hdHJpeChuY29sPTQsIG5yb3c9MCkpCiAgeCA8LSBjKCJhY2N1cmFjeSIsICJwcmVjaXNpb24iLCAicmVjYWxsIiwgIkYxIikKICBjb2xuYW1lcyhzdGF0aXN0aWNzKSA8LSB4CiAgbGV4MSA8LSAiYWZpbm5fYmluYXJ5IgogIGxleDIgPC0gImxzZF9iaW5hcnkiCiAgbGV4MyA8LSAidmFkZXJfYmluYXJ5IgogIGdvbGQgPC0gInJhdGluZ19iaW5hcnkiCiAgCiAgbGV4aWNvbnMgPC0gYyhsZXgxLGxleDIsbGV4MykKICAKICBmb3IobGV4IGluIGxleGljb25zKXsKICAgIGNvbmZ1c2lvbl9tYXRyaXggPC0gdGFibGUoQUNUVUFMPWRmW1tnb2xkXV0sIFBSRURJQ1RFRD1kZltbbGV4XV0pCiAgICBUTiA8LSBjb25mdXNpb25fbWF0cml4WzFdCiAgICBGTiA8LSBjb25mdXNpb25fbWF0cml4WzJdCiAgICBGUCA8LSBjb25mdXNpb25fbWF0cml4WzNdCiAgICBUUCA8LSBjb25mdXNpb25fbWF0cml4WzRdCiAgCiAgICAjIGNhbGN1bGF0ZSBzdGF0aXN0aWNzCiAgICBwcmVjaXNpb24gPC0gVFAvKFRQK0ZQKQogICAgYWNjdXJhY3kgPC0gKFRQK1ROKS8oVFArVE4rRlArRk4pCiAgICByZWNhbGwgPC0gVFAvKFRQK0ZOKQogICAgRjEgPC0gKDIqcHJlY2lzaW9uKnJlY2FsbCkvKHByZWNpc2lvbityZWNhbGwpCiAgCiAgICAjIGFkZCB0byB0YWJsZQogICAgb3V0cHV0IDwtIGMoYWNjdXJhY3kscHJlY2lzaW9uLCByZWNhbGwsIEYxKQogICAgc3RhdGlzdGljc1tsZXgsXSA9IHJiaW5kKHN0YXRpc3RpY3NbW2xleF1dLCBvdXRwdXQpCiAgICB9CiAgICAKICByZXR1cm4oc3RhdGlzdGljcykKICAKfQoKZ2V0X3N0YXRpc3RpY3ModGVzdCkKYGBgCgpgYGB7cn0KdGVzdFtbInZhZGVyX2JpbmFyeSJdXQpgYGAKCgpgYGB7cn0KbGV4aWNvbnMgPC0gbGlzdCh0ZXN0JGFmaW5uX2JpbmFyeSwgdGVzdCRsc2RfYmluYXJ5KQoKZm9yKGxleCBpbiBsZXhpY29ucyl7CiAgcHJpbnQobGV4KQp9CgpgYGAKCgoKCgoK